package cmich.cps.scheduler.algorithms;

import java.util.ArrayList; 
import java.util.HashMap;
import java.util.List;

import cmich.cps.scheduler.data.Course;
import cmich.cps.scheduler.data.Preference;
import cmich.cps.scheduler.data.Professor;
import cmich.cps.scheduler.data.Schedule;

/**
 * The Class Hybrid.
 * 
 * @author Jonathan Kissinger <kissi1j@cmich.edu>
 */
public class Hybrid extends Algorithm {
	
	/* (non-Javadoc)
	 * @see cmich.cps.scheduler.algorithms.Algorithm#start()
	 */
	@Override
	public void start() {
		LOGGER.fine("Starting Hybrid schedule generation algorithm.");
		this.max = getTheoreticalMax();
		algorithmName = "Hybrid";
		int value = 0;
		this.isFastMode = setIsFastMode();
		while(counter==0 && value<max && !isCancelled()) {
			counter = seed;
			LOGGER.fine("Generating schedule (" + seed + ") with Hybrid algorithm.");
			system.resetVariables();
			generateSchedule();
			if(validator.postValidate(schedule) && (bestSchedule==null || bestSchedule.getValue()<schedule.getValue())) {
				bestSchedule = schedule;
				value = bestSchedule.getValue();
			}
			seed++;
		}
		LOGGER.fine("Done generating schedules with hybrid algorithm.");
	}

	/* (non-Javadoc)
	 * @see cmich.cps.scheduler.algorithms.Algorithm#generateSchedule()
	 */
	@Override
	protected void generateSchedule() {
		this.processingTotal = system.getCourses().size();
		this.processingProgress = 0;
		schedule = new Schedule();
		schedule.setAlgorithmUsed(algorithmName);
		schedule.setValue(0);
		schedule.setTimestamp(validator.getTimestamp());
		schedule.setTheoreticalMax(max);
		manuallyAssignCourses();
		updateProgress();
		while(system.getCourses().size()>0 && !isCancelled()) {
			HashMap<String, Integer> courseValues = new HashMap<String, Integer>();
			List<Preference> coursePreferences = setCourseNegativePreferenceValues();
			for(int i=0;i<coursePreferences.size();i++) {
				Preference negativePreference = coursePreferences.get(i);
				Course course = (Course) system.getObject(negativePreference.getItem(), system.getCourses());
				Preference preference = setProfessorValues(course);
				if(preference!=null) {
					courseValues.put(preference.getItem(), preference.getValue() + negativePreference.getValue());
				}
			}
			List<Preference> courseTies = getHighestValueKeys(courseValues);
			if(courseTies.size()==0) {
				schedulingFailure();
				return;
			}
			else {
				assign(getNextTie(courseTies));
			}
		}
		schedule.setValue(getScheduleValue(schedule));
	}

	/* (non-Javadoc)
	 * @see cmich.cps.scheduler.algorithms.Algorithm#setProfessorValues(cmich.cps.scheduler.data.Course)
	 */
	@Override
	protected Preference setProfessorValues(Course course) {
		HashMap<String, Integer> professorValues = new HashMap<String, Integer>();
		List<Professor> professors = new ArrayList<Professor>();
		for(Professor professor : system.getProfessors()) {
			if(professor.getAssignedCourses()<professor.getMaxCourses()) {
				professors.add(professor);
			}
		}
		for(int i=0;i<professors.size();i++) {	
			Professor professor = professors.get(i);
			Preference preference = getBestRoomTimeslotCombo(professor, course);
			if(preference!=null) {
				int value = system.getPreferenceValue(professor, course.getName()) + preference.getValue();
				String key = course.getSectionNumber() + "::" + professor.getName() + "::" + preference.getItem();
				professorValues.put(key, value);
			}
		}
		List<Preference> professorTies = getHighestValueKeys(professorValues);
		return getNextTie(professorTies);
	}

	/* (non-Javadoc)
	 * @see cmich.cps.scheduler.algorithms.Algorithm#getBestRoomTimeslotCombo(cmich.cps.scheduler.data.Professor, cmich.cps.scheduler.data.Course)
	 */
	@Override
	protected Preference getBestRoomTimeslotCombo(Professor professor, Course course) {
		List<Preference> roomTimeslotsTies = getHighestValueKeys(getRoomTimeslotMap(professor, course));
		if(isFastMode) {
			if(roomTimeslotsTies.size()==0) {
				return null;
			}
			return roomTimeslotsTies.get(0);
		}
		return getNextTie(roomTimeslotsTies);
	}
}